package callback

import (
	"context"
	"sync"
	"time"

	"gitee.com/hasika/gotool/logx"
	"gitee.com/hasika/gotool/taskx"
)

type callbackNode[H ISeqCallback] struct {
	callback   taskx.Callback[H]
	expireTime time.Time
}

type Manager[H ISeqCallback] struct {
	callbackMap    map[int32]callbackNode[H]
	callbackMutex  sync.RWMutex
	expireDuration time.Duration
	ctx            context.Context
}

func NewCallbackManager[H ISeqCallback](ctx context.Context) *Manager[H] {
	c := &Manager[H]{
		callbackMap:    make(map[int32]callbackNode[H]),
		callbackMutex:  sync.RWMutex{},
		expireDuration: time.Second * 5,
		ctx:            ctx,
	}
	go c.loop()
	return c
}

func (c *Manager[H]) AddCallback(seq int32, callback func(rsp H, e error)) {
	node := callbackNode[H]{
		callback:   taskx.NewAsyncCall(callback),
		expireTime: time.Now().Add(c.expireDuration),
	}
	c.callbackMutex.Lock()
	c.callbackMap[seq] = node
	c.callbackMutex.Unlock()
}

func (c *Manager[H]) AddPromise(seq int32) taskx.Promise[H] {
	promise := taskx.NewPromise[H](c.ctx)
	node := callbackNode[H]{
		callback:   promise,
		expireTime: time.Now().Add(c.expireDuration),
	}
	c.callbackMutex.Lock()
	c.callbackMap[seq] = node
	c.callbackMutex.Unlock()
	return promise
}
func (c *Manager[H]) Trigger(m H) {
	seq := m.GetCallbackSeq()
	c.callbackMutex.RLock()
	callback, ex := c.callbackMap[seq]
	c.callbackMutex.RUnlock()
	if ex {
		callback.callback.OnTaskDone(m, nil)
		c.callbackMutex.Lock()
		delete(c.callbackMap, seq)
		c.callbackMutex.Unlock()
	}
}

func (c *Manager[H]) checkHMap() {
	invalidId := make([]int32, 0, 64)
	now := time.Now()
	func() {
		c.callbackMutex.RLock()
		defer c.callbackMutex.RUnlock()
		for id, node := range c.callbackMap {
			if now.After(node.expireTime) {
				invalidId = append(invalidId, id)

			}
		}
	}()
	func() {
		c.callbackMutex.Lock()
		defer c.callbackMutex.Unlock()
		for _, id := range invalidId {
			node, ex := c.callbackMap[id]
			if ex {
				delete(c.callbackMap, id)
				node.callback.Cancel()
				logx.Infof("clean time out callback")
			}
		}
	}()
}

func (c *Manager[H]) loop() {
	checkHMapTicker := time.NewTicker(time.Second * 10)
	defer checkHMapTicker.Stop()
	for {
		select {
		case <-c.ctx.Done():
			return
		case <-checkHMapTicker.C:
			c.checkHMap()
		}
	}
}

func (c *Manager[H]) CancelAll() {
	c.callbackMutex.Lock()
	defer c.callbackMutex.Unlock()
	for _, callback := range c.callbackMap {
		callback.callback.Cancel()
	}
	c.callbackMap = make(map[int32]callbackNode[H])
}
